#include <iostream>

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>

#define MAX_UDP_PACKET_SIZE 1500

typedef boost::array<unsigned char, MAX_UDP_PACKET_SIZE>    segment;

class connector;
class acceptor;

class connection_base
{
public:
    typedef boost::asio::ip::udp::socket    socket_type;
    typedef socket_type::protocol_type      protocol_type;
    typedef socket_type::endpoint_type      endpoint_type;
    typedef boost::system::error_code       error_type;
    typedef boost::asio::io_service         io_service;
};

class connection    :   public connection_base
{
public:

    explicit connection(io_service& ios)
    :   m_socket(ios)
    { }

    connection(io_service& ios, const endpoint_type& local)
    :   m_socket(ios)
    {
        m_socket.open(protocol_type::v4());
        m_socket.set_option(socket_type::reuse_address(true));
        m_socket.bind(local);
    }

    void close()
    {
        m_socket.close();
    }

    error_type close(error_type& ec)
    {
        return m_socket.close(ec);
    }

    error_type shutdown(socket_type::shutdown_type what, error_type& ec)
    {
        return m_socket.shutdown(what,ec);
    }

    void shutdown(socket_type::shutdown_type what)
    {
        m_socket.shutdown(what);
    }

    void bind(const endpoint_type& endpoint = endpoint_type())
    {
        m_socket.bind(endpoint);
    }

    error_type bind(const endpoint_type& endpoint, error_type& ec)
    {
        return m_socket.bind(endpoint,ec);
    }

    void open(const protocol_type& protocol)
    {
        m_socket.open(protocol);
    }

    error_type open(const protocol_type& protocol, error_type& ec)
    {
        return m_socket.open(protocol,ec);
    }

    io_service&    get_io_service()    {   return m_socket.get_io_service(); }

    template <typename ConstBufferSequence>
        std::size_t send(const ConstBufferSequence& buffers)
        {
            return m_socket.send(buffers);
        }

    template <typename ConstBufferSequence, typename WriteHandler>
        void async_send(const ConstBufferSequence& buffers, WriteHandler handler)
        {
            m_socket.async_send(buffers,async_send_handler<WriteHandler>(m_socket,handler));
        }

    template <typename MutableBufferSequence>
        std::size_t receive(const MutableBufferSequence& buffers)
        {
            return m_socket.receive(buffers);
        }

    template <typename MutableBufferSequence, typename ReadHandler>
        void async_receive(const MutableBufferSequence& buffers, ReadHandler handler)
        {
            m_socket.async_receive(buffers, async_read_handler<ReadHandler>(m_socket,handler));
        }

    bool    is_open()   const
    {
        return m_socket.is_open();
    }

private:


    friend class connector;
    friend class acceptor;

    template<typename WriteHandler>
        struct async_send_handler
        {
            async_send_handler(socket_type& s, WriteHandler handler)
            :   m_socket(s),
                m_handler(handler)
            { }

            void operator()(const error_type& error, std::size_t size)
            {
                using boost::asio::detail::bind_handler;
                m_socket.get_io_service().post(bind_handler(m_handler,error,size));
            }

            socket_type&   m_socket;
            WriteHandler   m_handler;
        };

    template<typename ReadHandler>
        struct async_read_handler
        {
            async_read_handler(socket_type& s, ReadHandler handler)
            :   m_socket(s),
                m_handler(handler)
            { }

            void operator()(const error_type& error, std::size_t size)
            {
                using boost::asio::detail::bind_handler;
                m_socket.get_io_service().post(bind_handler(m_handler,error,size));
            }

            socket_type&    m_socket;
            ReadHandler     m_handler;
        };

    segment         m_buffer;
    endpoint_type   m_remote_endpoint;
    socket_type     m_socket;
};

class acceptor  :   public connection_base
{
public:
    explicit acceptor(io_service& ios)
    :   m_socket(ios)
    { }

    acceptor(io_service& io_service, const endpoint_type& ep)
    :   m_socket(io_service,ep)
    {
         m_socket.set_option(socket_type::reuse_address(true));
    }


    template<typename AcceptHandler>
        void async_accept(connection& c, AcceptHandler handler)
        {

            if(!m_socket.is_open())
            {
                using boost::asio::detail::bind_handler;
                get_io_service().post(bind_handler(handler,boost::asio::error::bad_descriptor));
                return;
            }

            if(c.is_open())
            {
                using boost::asio::detail::bind_handler;
                get_io_service().post(bind_handler(handler,boost::asio::error::already_connected));
                return;
            }
            m_socket.async_receive_from(boost::asio::buffer(c.m_buffer),c.m_remote_endpoint,
                                        accept_receive_handler<AcceptHandler>(*this,c,handler));
        }

    void accept(connection& c)
    {
        error_type ec;
        accept(c,ec);
        boost::asio::detail::throw_error(ec);
    }

    error_type accept(connection& c, error_type& ec)
    {
        m_socket.receive_from(boost::asio::buffer(c.m_buffer),c.m_remote_endpoint,0,ec);
        return ec;
    }

    endpoint_type local_endpoint() const
    {
        return m_socket.local_endpoint();
    }

    void bind(const endpoint_type& endpoint = endpoint_type())
    {
        m_socket.bind(endpoint);
    }

    error_type bind(const endpoint_type& endpoint, error_type& ec)
    {
        return m_socket.bind(endpoint,ec);
    }

    void open(const protocol_type& protocol)
    {
        m_socket.open(protocol);
    }

    void close()
    {
        m_socket.close();
    }

    error_type close(error_type& ec)
    {
        return m_socket.close(ec);
    }

    error_type  shutdown(socket_type::shutdown_type what, error_type& ec)
    {
        return m_socket.shutdown(what,ec);
    }

    void shutdown(socket_type::shutdown_type what)
    {
        m_socket.shutdown(what);
    }

    error_type open(const protocol_type& protocol, error_type& ec)
    {
        return m_socket.open(protocol,ec);
    }

    io_service&    get_io_service()    {   return m_socket.get_io_service(); }

    /** \brief Cancel all asynchronous operations associated with the acceptor.
     */
    void cancel()
    {
        m_socket.cancel();
    }
private:
    static segment& access_buffer(connection& c)
    {
        return c.m_buffer;
    }

    static endpoint_type& access_remote_endpoint(connection& c)
    {
        return c.m_remote_endpoint;
    }

    static socket_type& access_socket(connection& c)
    {
        return c.m_socket;
    }

    template<typename AcceptHandler>
        struct accept_send_handler
        {
            accept_send_handler(acceptor& a, connection& c, AcceptHandler handler)
            :   m_acceptor(a),
                m_c(c),
                m_handler(handler)
            { }

            void operator()(const error_type& error, std::size_t size)
            {
                error_type ec(error);
                if(!ec)
                {
                    std::cout << ">> acceptor: establishing new connection\n";
                    endpoint_type   local_endpoint(m_acceptor.m_socket.local_endpoint());
                    socket_type&    s(access_socket(m_c));

                    if(!s.open(local_endpoint.protocol(),ec))
                    {
                        if(     !s.set_option(typename socket_type::reuse_address(true),ec)
                            &&  !s.bind(local_endpoint,ec)
                            &&  !s.connect(access_remote_endpoint(m_c),ec))
                        {
                            using boost::asio::detail::bind_handler;
                            m_acceptor.m_socket.get_io_service().post(bind_handler(m_handler,ec));
                            return;
                        }

                        s.close();
                    }
                    else
                        std::cout << ">> acceptor: Oopsie: "<< ec << "\n";
                }
                else
                    std::cout << ">> acceptor: establishing failed\n";

                using boost::asio::detail::bind_handler;
                m_acceptor.m_socket.get_io_service().post(bind_handler(m_handler,ec));
            }
            acceptor&       m_acceptor;
            connection&     m_c;
            AcceptHandler   m_handler;
        };

    template<typename AcceptHandler>
        struct accept_receive_handler
        {
            accept_receive_handler(acceptor& a, connection& c, AcceptHandler handler)
            :   m_acceptor(a),
                m_c(c),
                m_handler(handler)
            { }

            void operator()(const error_type& error, std::size_t size)
            {
                if(error)
                {
                    using boost::asio::detail::bind_handler;
                    m_acceptor.m_socket.get_io_service().post(bind_handler(m_handler,error));
                }
                else
                {
                    std::cout << ">> acceptor: got a connection request\n";
                    //TODO: 
                    m_acceptor.m_socket.async_send_to(  boost::asio::buffer(access_buffer(m_c)),access_remote_endpoint(m_c),
                                                        accept_send_handler<AcceptHandler>(m_acceptor,m_c,m_handler));
                }
            }
            acceptor&       m_acceptor;
            connection&     m_c;
            AcceptHandler   m_handler;
        };

    socket_type    m_socket;
};

class connector :   public connection_base
{
public:
    template<class ConnectHandler>
        void async_connect(connection& c, const endpoint_type& remote, ConnectHandler handler)
        {
            error_type   ec;
            using boost::asio::detail::bind_handler;
            if(c.m_socket.connect(remote,ec))
                c.m_socket.get_io_service().post(bind_handler(handler,ec));
        }

    void connect(connection& c, const endpoint_type& remote)
    {
        error_type   ec;
        connect(c,remote,ec);
        boost::asio::detail::throw_error(ec);
    }

    error_type connect(connection& c, const endpoint_type& remote, error_type& ec)
    {
        using boost::asio::detail::bind_handler;
        if(c.m_socket.connect(remote,ec))
            return ec;
    }

private:

    void handle_connect(connection* c, const error_type& error)
    {
        if(error)
        {
            std::cout << ">> connector could not connect\n";
            delete c;
        }
        else
        {
            c->m_socket.async_send(  boost::asio::buffer(c->m_buffer),
                                                    boost::bind(&connector::handle_send,this,c,
                                                                boost::asio::placeholders::error,
                                                                boost::asio::placeholders::bytes_transferred));
        }
    }

    void handle_send(connection* c, const boost::system::error_code& error, std::size_t len)
    {
        if(error)
        {
            std::cout << ">> connector could not connect\n";
            delete c;
        }
        else
        {
            std::cout << ">> connector: sent a connection request\n";
            c->m_socket.async_receive(  boost::asio::buffer(c->m_buffer),
                                                    boost::bind(&connector::handle_receive,this,c,
                                                                boost::asio::placeholders::error,
                                                                boost::asio::placeholders::bytes_transferred));
        }
    }

    void handle_receive(connection* c, const boost::system::error_code& error, std::size_t len)
    {
        if(error)
        {
            std::cout << ">> connector could not connect\n";
            delete c;
        }
        else
        {
            std::cout << ">> connector: connection request accepted\n";
        }
    }
};

void handle_connect(connection& c, boost::system::error_code& ec)
{
    std::string m("Hello World!");
    c.send(boost::asio::buffer(m));
}

void client_loop(boost::asio::io_service& ios, const boost::asio::ip::udp::endpoint& dest)
{
    connector co;
    connection c(ios);
    co.async_connect(c,dest,boost::bind(handle_connect,boost::ref(c),boost::asio::placeholders::error));
    while(true) {}
}

struct base
{
    typedef void(*invoke_ptr)(base*);
    base(invoke_ptr f)
    :   func(f)
    { }

    void invoke()
    {
        func(this);
        std::cout << sizeof(func) << "\n";
    }
private:
    invoke_ptr  func;
};

template<class T>
    struct handler  :   public base
    {
        handler()
        :   base(&handler<T>::invoke_impl)
        { }
        //handler
        void doit()
        {
            std::cout << "Ha! Tschack!\n";
        }
    private:
        static void invoke_impl(base* b)
        {
            static_cast<handler<T>*>(b)->doit();
        }
    };

boost::array<char,30>    mess;

void message_handler(const boost::system::error_code& e, std::size_t size)
{
    if(!e)
    {
        std::string m(mess.begin(),mess.end());
        std::cout << "Received message: " << m << "\n";
    }
    else
        std::cout << "Receive Error: "<< e.message() << std::endl;
    exit(EXIT_SUCCESS);
}

void connected_handler(connection& c, const boost::system::error_code& error)
{
    if(!error)
    {
        std::cout << ">> Connection accepted" << std::endl;
        c.async_receive(boost::asio::buffer(mess),&message_handler);
    }
    else
        std::cout << ">> Connection accept failed: "<< error.message() << std::endl;
}


int main()
{ 
    boost::asio::io_service ios;
    boost::asio::ip::udp::endpoint acc_endpoint(boost::asio::ip::address::from_string("127.0.0.1"),8600);//boost::asio::ip::udp::v4(),8600);

    acceptor    acc(ios,acc_endpoint);
    connection c(ios);
    acc.async_accept(c,boost::bind(&connected_handler,boost::ref(c),boost::asio::placeholders::error));

    boost::thread client_thread(boost::bind(&client_loop,boost::ref(ios),boost::ref(acc_endpoint)));

    while(true)
        try {
            ios.run();
        }
        catch(const boost::system::system_error& e)
        {
            std::cout << ">> System error: " << e.what() << std::endl;
            exit(EXIT_FAILURE);
        }
    return 0;
}
